// synchronous fifo

`timescale 1ns/1ps

module sync_fifo
#(parameter
    DWIDTH     = 8,
    DEPTH      = 256,
    SHOW_AHEAD = 0,
    RAM_STYLE  = "block",
    AWIDTH     = log2(DEPTH)
)
(
    // system signal
    input  wire              I_sclk,
    input  wire              I_rst_n,
    input  wire              I_reset,

    // write
    output wire              O_full,
    input  wire              I_wrreq,
    input  wire [DWIDTH-1:0] I_data,

    // read
    output wire              O_empty,
    input  wire              I_rdreq,
    output wire [DWIDTH-1:0] O_q,

    // used words
    output wire [AWIDTH-1:0] O_usedw
);
//------------------------Parameter----------------------

//------------------------Local signal-------------------
(* ram_style = RAM_STYLE *)
reg  [DWIDTH-1:0] mem[0:DEPTH-1];
reg  [DWIDTH-1:0] q_buf;
reg  [AWIDTH-1:0] waddr;
reg  [AWIDTH-1:0] raddr;
wire [AWIDTH-1:0] wnext;
wire [AWIDTH-1:0] rnext;
reg  [AWIDTH-1:0] usedw;
reg               full;
reg               empty;

//------------------------Instantiation------------------

//------------------------Task and function--------------
function integer log2;
    input  [ 31: 0] value;
    begin
    for (log2 = 0; value > 0; log2 = log2 + 1)
        value = value >> 1;
    end
endfunction

//------------------------Body---------------------------
assign O_full  = full;
assign O_empty = empty;
assign O_usedw = usedw;

assign wnext   = !(~full & I_wrreq)   ? waddr :
                 (waddr == DEPTH - 1) ? 1'b0  :
                 waddr + 1'b1;
assign rnext   = !(~empty & I_rdreq)  ? raddr :
                 (raddr == DEPTH - 1) ? 1'b0  :
                 raddr + 1'b1;

// waddr
always @(posedge I_sclk) begin
    if (~I_rst_n)
        waddr <= 1'b0;
    else if (I_reset)
        waddr <= 1'b0;
    else
        waddr <= wnext;
end

// raddr
always @(posedge I_sclk) begin
    if (~I_rst_n)
        raddr <= 1'b0;
    else if (I_reset)
        raddr <= 1'b0;
    else
        raddr <= rnext;
end

// usedw
always @(posedge I_sclk) begin
    if (~I_rst_n)
        usedw <= 1'b0;
    else if (I_reset)
        usedw <= 1'b0;
    else if ((~full & I_wrreq) & ~(~empty & I_rdreq)) // only write
        usedw <= usedw + 1'b1;
    else if (~(~full & I_wrreq) & (~empty & I_rdreq)) // only read
        usedw <= usedw - 1'b1;
end


// full
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        full <= 1'b0;
    else if (I_reset)
        full <= 1'b0;
    else if ((~full & I_wrreq) & ~(~empty & I_rdreq)) // only write
        full <= (usedw == DEPTH - 1);
    else if (~(~full & I_wrreq) & (~empty & I_rdreq)) // only read
        full <= 1'b0;
end

// empty
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        empty <= 1'b1;
    else if (I_reset)
        empty <= 1'b1;
    else if ((~full & I_wrreq) & ~(~empty & I_rdreq)) // only write
        empty <= 1'b0;
    else if (~(~full & I_wrreq) & (~empty & I_rdreq)) // only read
        empty <= (usedw == 1'b1);
end

// mem
always @(posedge I_sclk) begin
    if (~full & I_wrreq)
        mem[waddr] <= I_data;
end

generate
if (SHOW_AHEAD) begin : gen_show_ahead_q
    reg [DWIDTH-1:0] q_tmp;
    reg              show_ahead;

    assign O_q = show_ahead? q_tmp : q_buf;

    // q_buf
    always @(posedge I_sclk) begin
        q_buf <= mem[rnext];
    end

    // q_tmp
    always @(posedge I_sclk) begin
        if (~full & I_wrreq)
            q_tmp <= I_data;
    end

    // show_ahead
    always @(posedge I_sclk) begin
        if (~I_rst_n)
            show_ahead <= 1'b0;
        else if (I_reset)
            show_ahead <= 1'b0;
        else if (~full & I_wrreq)
            show_ahead <= (waddr == rnext);
        else
            show_ahead <= 1'b0;
    end
end
else begin : gen_normal_q
    assign O_q = q_buf;

    // q_buf
    always @(posedge I_sclk) begin
        if (~empty & I_rdreq)
            q_buf <= mem[raddr];
    end
end

endgenerate

endmodule

// vim:tw=0 ts=4 sw=4 et:
